/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
#include "gimisubscriptions.h"
#include "gimicore.h"

namespace gimi {

subscriptionInfo::subscriptionInfo(const int uInterval)
  : updateInterval(uInterval),
    nextUpdate(gim::time(true, uInterval))
{
  nextUpdate.setToCurrentPlus(uInterval);
}
  
GIMISubscriptions::GIMISubscriptions()
  : subscriptions(),
    subscriptionMutex(ownMutex_Init())
{
  assert(subscriptionMutex != NULL);
}

GIMISubscriptions::~GIMISubscriptions()
{
  ownMutex_Destroy(subscriptionMutex);
}
    
bool GIMISubscriptions::addSubscription(const int clientId,
                                        const int majorTypeId,
                                        const int minorTypeId,
                                        const int timeoutInterval)
{
  bool result = false;
  t_subscriptionsIterator subIter;
  t_subscriptionsMajorTypesIterator majorIter;
  //t_subscriptionsMinorTypesIterator minorIter;
  std::pair< t_subscriptionsMinorTypesIterator, bool > resultPair;
  
  ownMutex_Lock(subscriptionMutex);
  // Find clientid and insert if necessary.
  subIter = subscriptions.find(clientId);
  if (subIter == subscriptions.end()) { // If clientId was not found, add it.
    subIter = (subscriptions.insert(t_subscriptionsPair(clientId,
               t_subscriptionsMajorTypes()))).first;
  }
  // Find majortype and insert if necessary.
  majorIter = (subIter->second).find(majorTypeId);
  if (majorIter == (subIter->second).end()) { // If majorType was not found, add it.
    majorIter = ((subIter->second).insert(t_subscriptionsMajorPair(majorTypeId,
                                          t_subscriptionsMinorTypes()))).first;
  }
  
  // Try to add subscription.
  resultPair = (majorIter->second).insert(t_subscriptionsMinorPair(minorTypeId,
                                          subscriptionInfo(timeoutInterval)));
  
  if (!resultPair.second) { // Insert failed, subscription already exists, change timeout to new value.
    dPrint(20, "Subscription for %d:%d on 0x%08x already existed, updating "
        "timeoutinterval.", majorTypeId, minorTypeId, timeoutInterval);
    // Calculate new time for nextUpdate.
    if (timeoutInterval < resultPair.first->second.updateInterval) {
      resultPair.first->second.nextUpdate.reduceTimeMs(
          resultPair.first->second.updateInterval - timeoutInterval);
    } else {
      resultPair.first->second.nextUpdate.addTimeMs(
          timeoutInterval - resultPair.first->second.updateInterval);
    }
    resultPair.first->second.updateInterval = timeoutInterval;
  }
  
  ownMutex_Unlock(subscriptionMutex);
  return result;
}
    
bool GIMISubscriptions::removeSubscription(const int clientId,
                                           const int majorTypeId,
                                           const int minorTypeId)
{
  bool result = false;
  t_subscriptionsIterator subIter;
  t_subscriptionsMajorTypesIterator majorIter;
  
  ownMutex_Lock(subscriptionMutex);
  subIter = subscriptions.find(clientId);
  if (subIter != subscriptions.end()) { // Found clientId
    majorIter = (subIter->second).find(majorTypeId);
    if (majorIter != (subIter->second).end()) { // Found majorType
      if ((majorIter->second).erase(minorTypeId) == 1) { // Found and removed minorType (subscription)
        result = true;
      }
    }
  }
  
  ownMutex_Unlock(subscriptionMutex);
  return result;
}

bool GIMISubscriptions::isSubscription(const int clientId,
                                      const int majorTypeId,
                                      const int minorTypeId)
{
  bool result = false;
  t_subscriptionsIterator subIter;
  t_subscriptionsMajorTypesIterator majorIter;
  t_subscriptionsMinorTypesIterator minorIter;
  
  ownMutex_Lock(subscriptionMutex);
  subIter = subscriptions.find(clientId);
  if (subIter != subscriptions.end()) { // Found clientId
    majorIter = (subIter->second).find(majorTypeId);
    if (majorIter != (subIter->second).end()) { // Found majorType
      minorIter = (majorIter->second).find(minorTypeId);
      if (minorIter != (majorIter->second).end()) { // Found minorType
        result = true;
      }
    }
  }
  
  ownMutex_Unlock(subscriptionMutex);
  return result;
}

bool GIMISubscriptions::updateSubscriptionTime(const int clientId,
                                               const int majorTypeId,
                                               const int minorTypeId)
{
  bool result = false;
  t_subscriptionsIterator subIter;
  t_subscriptionsMajorTypesIterator majorIter;
  t_subscriptionsMinorTypesIterator minorIter;
  
  ownMutex_Lock(subscriptionMutex);
  subIter = subscriptions.find(clientId);
  if (subIter != subscriptions.end()) { // Found clientId
    majorIter = (subIter->second).find(majorTypeId);
    if (majorIter != (subIter->second).end()) { // Found majorType
      minorIter = (majorIter->second).find(minorTypeId);
      if (minorIter != (majorIter->second).end()) { // Found minorType
        if ((minorIter->second).updateInterval > 0) {
          // Renew updatetime.
          (minorIter->second).nextUpdate.setToCurrentPlus((minorIter->second).updateInterval);
        }
        result = true;
      }
    }
  }
  
  ownMutex_Unlock(subscriptionMutex);
  return result;
}
   
void GIMISubscriptions::doResubscriptions(GIMICore *gc)
{
  ownMutex_Lock(subscriptionMutex);
  gim::time currentTime(true);
  for (t_subscriptionsIterator subIter = subscriptions.begin(); // Loop clientids
       subIter != subscriptions.end();
       ++subIter)
  {
    for (t_subscriptionsMajorTypesIterator majorIter = (subIter->second).begin(); // Loop majortypes
         majorIter != (subIter->second).end();
         ++majorIter)
    {
      for (t_subscriptionsMinorTypesIterator minorIter = (majorIter->second).begin(); // Loop minortypes/subscriptions
           minorIter != (majorIter->second).end();
           ++minorIter)
      {
        if ((minorIter->second).updateInterval > 0) { // If we should update.
          if ((minorIter->second).nextUpdate < currentTime) { // If timeout has expired.
            dPrint(30, "Updating subscription to service %d:%d on client 0x%08x",
                   majorIter->first, minorIter->first, subIter->first);
            
            // Renew updatetime.
            (minorIter->second).nextUpdate = currentTime;
            (minorIter->second).nextUpdate.addTimeMs((minorIter->second).updateInterval);
            
            // Send subscription.
            if (gc->subscribeCore(subIter->first, majorIter->first,
                                  minorIter->first, false, 0) != GIMI_TIMEOUT)
            {
              dPrint(6, "Failed to send resubscription to client 0x%08x for service %d:%d",
                     subIter->first, majorIter->first, minorIter->first);
            } else {
              dPrint(6, "Resubscription sent to client 0x%08x for service %d:%d",
                     subIter->first, majorIter->first, minorIter->first);
            }
          }
        }
      }
    }
  }
  ownMutex_Unlock(subscriptionMutex);
}

} // end namespace gimi
